#region File Description
//-----------------------------------------------------------------------------
// ModelViewerControl.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.ComponentModel;
using System.Windows.Forms;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Content;

namespace ModelViewer
{
    /// <summary>
    /// Example control inherits from GraphicsDeviceControl, and displays
    /// a spinning 3D model. The main form class is responsible for loading
    /// the model: this control just displays it.
    /// </summary>
    public class ModelViewerControl : GraphicsDeviceControl
    {
        #region Enums

        /// <summary>
        /// The view types
        /// </summary>
        public enum ViewTypes { 
            /// <summary>
            /// A single window using perspective projection
            /// </summary>
            Normal = 0, 
            
            /// <summary>
            /// Four windows with perspective, top, right, and front views
            /// </summary>
            Four = 1 }

        #endregion

        #region Fields

        /// <summary>
        /// The list of models we are drawing. ViewerModel is a wrapper for an XNA
        /// model with additional capabilities
        /// </summary>
        private List<ViewerModel> models = new List<ViewerModel>();

        /// <summary>
        /// Timer for any animation support
        /// </summary>
        Stopwatch timer;

        /// <summary>
        /// The time of the last draw so we can support animation
        /// </summary>
        private TimeSpan lastTime;

        /// <summary>
        /// A line draw object
        /// </summary>
        LineDraw lineDraw;

        /// <summary>
        /// A content manager used to load additional resources this view uses
        /// </summary>
        ContentManager content;

        /// <summary>
        /// The application main form
        /// </summary>
        private MainForm mainForm = null;

        /// <summary>
        /// A list of list of views of the models, each in a rectangle on the screen
        /// There is one for each view type.
        /// </summary>
        private List<List<View>> views = new List<List<View>>();

        /// <summary>
        /// The current view type
        /// </summary>
        private ViewTypes viewType = ViewTypes.Normal;



        #endregion

        #region Properties

        /// <summary>
        /// The list of models we are drawing
        /// </summary>
        [Browsable(false)]
        public List<ViewerModel> Models
        {
            get { return models; }
        }

        /// <summary>
        /// The application main form
        /// </summary>
        [Browsable(false)]
        public MainForm MainForm { get { return mainForm; } set { mainForm = value; } }

        [Browsable(false)]
        public ViewTypes ViewType
        {
            get { return viewType; }
            set { viewType = value;}
        }

        /// <summary>
        /// The current view set
        /// </summary>
        [Browsable(false)]
        public List<View> ViewSet { get { return views[(int)viewType]; } }
        
        #endregion

        #region Construction and Initialization

        /// <summary>
        /// Initializes the control.
        /// </summary>
        protected override void Initialize()
        {
            content = new ContentManager(Services);
            content.RootDirectory = "Content";

            SetupViews();

            //camera = new Camera();
            //camera.AutoDepth = true;
            //camera.Center = Vector3.Zero;
            //camera.Eye = new Vector3(1, 1, 1) * 100;
            //camera.ZNear = camera.CameraDistance - 200;
            //camera.ZFar = camera.CameraDistance + 1000;
            //camera.Fov = MathHelper.ToRadians(20);

            lineDraw = new LineDraw();
            lineDraw.LoadContent(content);

            // Start the animation timer.
            timer = Stopwatch.StartNew();
            lastTime = timer.Elapsed;

            // Hook the idle event to constantly redraw our animation.
            Application.Idle += delegate { Invalidate(); };

            // Install the mouse wheel handler
            this.MouseWheel += new MouseEventHandler(this.OnMouseWheel);
            this.MouseMove += new MouseEventHandler(this.OnMouseMove);
            this.MouseDown += new MouseEventHandler(this.OnMouseDown);
            this.MouseUp += new MouseEventHandler(this.OnMouseUp);
        }

        #endregion

        #region View Support

        /// <summary>
        /// Create and set up all of the view options
        /// </summary>
        private void SetupViews()
        {
            views.Clear();

            // Normal view
            List<View> viewNormal = new List<View>();
            views.Add(viewNormal);
            viewNormal.Add(new View());

            // Four view 
            List<View> viewFour = new List<View>();
            views.Add(viewFour);
            viewFour.Add(new View()); 
            viewFour.Add(new View()); 
            viewFour.Add(new View()); 
            viewFour.Add(new View());
        }

        /// <summary>
        /// Each view has an associated viewport. This function sets the viewports correctly before
        /// we draw for each view.
        /// </summary>
        private void SetViewViewports()
        {
            switch (viewType)
            {
                case ViewTypes.Normal:
                    ViewSet[0].SetViewport(this.Location, this.Size);
                    break;

                case ViewTypes.Four:
                    // TODO
                    break;
            }
        }

        #endregion

        #region Models Support

        /// <summary>
        /// Add a model to the model collection
        /// </summary>
        /// <param name="model"></param>
        public void AddModel(ViewerModel model)
        {
            Models.Add(model);

            // If this is the first model, set the camera up
            if (Models.Count == 1)
            {
                foreach (List<View> viewSet in views)
                {
                    model.SetInitialCamera(viewSet[0].Camera);
                }
            }

            BoundingBox bb = GetBoundingBox(model.Model);
            BoundingBoxRenderer.Instance.Initialize(GraphicsDevice, bb);
        }

        BoundingBox GetBoundingBox(Model model)
        {
            // Initialize minimum and maximum corners of the bounding box to max and min values
            Vector3 min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
            Vector3 max = new Vector3(float.MinValue, float.MinValue, float.MinValue);

            // For each mesh of the model
            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (ModelMeshPart meshPart in mesh.MeshParts)
                {
                    // Vertex buffer parameters
                    int vertexStride = meshPart.VertexBuffer.VertexDeclaration.VertexStride;
                    int vertexBufferSize = meshPart.NumVertices * vertexStride;

                    // Get vertex data as float
                    float[] vertexData = new float[vertexBufferSize / sizeof(float)];
                    meshPart.VertexBuffer.GetData<float>(vertexData);

                    // Iterate through vertices (possibly) growing bounding box, all calculations are done in world space
                    for (int i = 0; i < vertexBufferSize / sizeof(float); i += vertexStride / sizeof(float))
                    {
                        Vector3 transformedPosition = Vector3.Transform(new Vector3(vertexData[i], vertexData[i + 1], vertexData[i + 2]), mesh.ParentBone.Transform);

                        min = Vector3.Min(min, transformedPosition);
                        max = Vector3.Max(max, transformedPosition);
                    }
                }
            }

            // Create and return bounding box
            return new BoundingBox(min, max);
        }

        /// <summary>
        /// Remove a model from the models collection
        /// </summary>
        /// <param name="model"></param>
        public void RemoveModel(ViewerModel model)
        {
            Models.Remove(model);
        }

        #endregion

        #region Drawing

        /// <summary>
        /// Draws the control.
        /// </summary>
        protected override void Draw()
        {
            // Ensure the viewports are properly set
            SetViewViewports();

            // Get settings information into correct local color type
            Color backColor = new Color(244, 255, 255, 0);
            Color axisColorX = Color.Red;
            Color axisColorY = Color.Blue;
            Color axisColorZ = Color.Green;
            Color ambientColor = Color.White;
            if (MainForm != null)
            {
                System.Drawing.Color backWindowsColor = MainForm.Settings.BackgroundColor;
                backColor = new Color(backWindowsColor.R, backWindowsColor.G, backWindowsColor.B);
                
                System.Drawing.Color axisWindowsColorX = MainForm.Settings.AxisColorX;
                axisColorX = new Color(axisWindowsColorX.R, axisWindowsColorX.G, axisWindowsColorX.B);
                System.Drawing.Color axisWindowsColorY = MainForm.Settings.AxisColorY;
                axisColorY = new Color(axisWindowsColorY.R, axisWindowsColorY.G, axisWindowsColorY.B);
                System.Drawing.Color axisWindowsColorZ = MainForm.Settings.AxisColorZ;
                axisColorZ = new Color(axisWindowsColorZ.R, axisWindowsColorZ.G, axisWindowsColorZ.B);
                System.Drawing.Color ambientWinowsColor = MainForm.Settings.AmbientColor;
                ambientColor = new Color(ambientWinowsColor.R, ambientWinowsColor.G, ambientWinowsColor.B);
            }


            //
            // Updates support
            //
            TimeSpan newTime = timer.Elapsed;
            GameTime gameTime = new GameTime(newTime, newTime - lastTime);
            lastTime = newTime;

            
            //
            // Drawing
            //

            // Clear to the control background color.
            GraphicsDevice.Clear(backColor);

            //
            // Draw the views
            //

            foreach(View view in ViewSet)
            {
                //
                // Line draw support
                //
                lineDraw.Clear();
                lineDraw.Axis(1000, axisColorX, axisColorY, axisColorZ);

                foreach (ViewerModel model in Models)
                {
                    model.Draw(GraphicsDevice, view.Camera, lineDraw, ambientColor);
                }
            
                lineDraw.Draw(GraphicsDevice, view.Camera);
                BoundingBoxRenderer.Instance.Draw(view.Camera);
            }

        }

        #endregion

        int lastX = 0;
        int lastY = 0;
        private View mouseView = null;

        /// <summary>
        /// Retain the last window we clicked on for the mouse wheel
        /// </summary>
        private View lastMouseView = null;

        private void OnMouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            // What view did we click on?
            foreach (View view in ViewSet)
            {
                if (view.InView(new System.Drawing.Point(e.X, e.Y)))
                {
                    mouseView = view;
                    lastMouseView = view;
                    return;
                }
            }
        }

        private void OnMouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            mouseView = null;
        }

        private void OnMouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            this.Focus();

            int dx = e.X - lastX;
            int dy = e.Y - lastY;

            if (mouseView != null)
            {
                Camera camera = mouseView.Camera;

                if (Control.MouseButtons == MouseButtons.Left)
                {
                    camera.MouseAction(dx, dy, Camera.MouseMode.PITCHYAW);
                }
                else if (Control.MouseButtons == MouseButtons.Right)
                {
                    mouseView.Translate(lastX, lastY, e.X, e.Y);
                }
                else if (Control.MouseButtons == (MouseButtons.Right | MouseButtons.Left))
                {
                    camera.MouseWheelAction(-(dx + dy));
                }
            }

            lastX = e.X;
            lastY = e.Y;
        }

        private void OnMouseWheel(object sender, System.Windows.Forms.MouseEventArgs e)
        {

            if (lastMouseView != null)
            {
                Camera camera = lastMouseView.Camera;

                // Handle the mouse wheel
                if (e.Delta > 0)
                {
                    camera.MouseWheelAction(e.Delta);
                }
                else
                {
                    camera.MouseWheelAction(e.Delta * 0.1f);
                }
            }
        }

    }
}
